﻿using System.IO.Ports;

namespace Yewan.Modbus
{
    public class ModbusRtu : ModbusSerial
    {
        public ModbusRtu(string portName, int baudRate)
            : this(portName, baudRate, Parity.None, 8, StopBits.One)
        {
        }

        public ModbusRtu(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
            : base()
        {
            PortName = portName;
            BaudRate = baudRate;
            Parity = parity;
            DataBits = dataBits;
            StopBits = stopBits;
        }

        /// <summary>
        /// 读取
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="code">功能码</param>
        /// <param name="startAddress">寄存器起始地址</param>
        /// <param name="count">寄存器的个数</param>
        /// <returns></returns>
        public override byte[] Read(byte slave, FunctionCode code, ushort startAddress, ushort count)
        {
            //分配 8 字节空间来构造最终发送报文
            Span<byte> sendBytes = stackalloc byte[8];
            BuildHeader(sendBytes, slave, code, startAddress, count); //构建请求头
            var contentBytes = sendBytes[..6]; //请求内容
            var crcBytes = Crc16(contentBytes); //2位CRC校验码
            crcBytes.CopyTo(sendBytes[6..]);

            var receiveLength = code switch
            {
                FunctionCode.ReadCoilStatus or FunctionCode.ReadInputCoils => (int)Math.Ceiling(count / 8.0) + 5, //01/02功能码
                _ => count * 2 + 5 //03/04功能码
            };
            var receiveBytes = SendAndReceive(sendBytes.ToArray(), receiveLength);
            var dataBytes = MessageCheck(receiveBytes, receiveLength);
            return dataBytes;
        }

        /// <summary>
        /// 写入
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="code">功能码</param>
        /// <param name="address">寄存器地址</param>
        /// <param name="value">布尔值</param>
        /// <returns></returns>
        public override byte[] Write(byte slave, FunctionCode code, ushort address, bool value)
        {
            ushort coilValue = value ? (ushort)0xFF00 : (ushort)0x0000;
            var receiveBytes = Write(slave, code, address, coilValue);
            return receiveBytes;
        }

        /// <summary>
        /// 写入
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="code">功能码</param>
        /// <param name="address">寄存器地址</param>
        /// <param name="value">数据值</param>
        /// <returns></returns>
        public override byte[] Write(byte slave, FunctionCode code, ushort address, ushort value)
        {
            //分配 8 字节空间来构造最终发送报文
            Span<byte> sendBytes = stackalloc byte[8];
            BuildHeader(sendBytes, slave, code, address, value); //构建请求头
            var contentBytes = sendBytes[..6]; //请求内容
            var crcBytes = Crc16(contentBytes); //2位CRC校验码
            crcBytes.CopyTo(sendBytes[6..]);

            var receiveBytes = SendAndReceive(sendBytes.ToArray(), 8);
            return receiveBytes;
        }

        public override byte[] Write(byte slave, FunctionCode code, ushort startAddress, bool[] values)
        {
            if (values == null || values.Length == 0 || values.Length > 1968)
                throw new ArgumentOutOfRangeException(nameof(values), "线圈数量必须在1~1968之间");

            int byteCount = (values.Length + 7) / 8;
            int messageLength = 7 + byteCount + 2;

            Span<byte> sendBytes = messageLength <= 256 ? stackalloc byte[messageLength] : new byte[messageLength];
            BuildHeader(sendBytes, slave, code, startAddress, (ushort)values.Length, (byte)byteCount); //构建请求头

            for (int i = 0; i < values.Length; i++)
            {
                if (values[i])
                    sendBytes[7 + i / 8] |= (byte)(1 << (i % 8));
            }

            var crcBytes = Crc16(sendBytes[..(7 + byteCount)]);
            crcBytes.CopyTo(sendBytes);

            var receiveBytes = SendAndReceive(sendBytes.ToArray(), 8);
            return receiveBytes;
        }

        public override byte[] Write(byte slave, FunctionCode code, ushort startAddress, ushort[] values)
        {
            if (values == null || values.Length == 0 || values.Length > 123)
                throw new ArgumentOutOfRangeException(nameof(values), "最多写入123个寄存器");

            int byteCount = values.Length * 2;
            int messageLength = 7 + byteCount + 2;

            Span<byte> sendBytes = messageLength <= 256 ? stackalloc byte[messageLength] : new byte[messageLength];
            BuildHeader(sendBytes, slave, code, startAddress, (ushort)values.Length, (byte)byteCount); //构建请求头

            for (int i = 0; i < values.Length; i++)
            {
                var nextByteIndex = 7 + i * 2;
                sendBytes[nextByteIndex] = (byte)(values[i] >> 8);
                sendBytes[nextByteIndex + 1] = (byte)(values[i] & 0xFF);
            }

            var crcBytes = Crc16(sendBytes[..(7 + byteCount)]);
            crcBytes.CopyTo(sendBytes);

            var receiveBytes = SendAndReceive(sendBytes.ToArray(), 8);
            return receiveBytes;
        }

        /// <summary>
        /// CRC16校验字节
        /// </summary>
        /// <param name="data">数据</param>
        /// <param name="isBigEndian">是否大端序</param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        private byte[] Crc16(ReadOnlySpan<byte> data, bool isBigEndian = false)
        {
            if (data.Length == 0)
                throw new ArgumentException("数据不能为空", nameof(data));

            ushort crc = 0xFFFF;
            foreach (byte b in data)
            {
                crc ^= b;
                for (int j = 0; j < 8; j++)
                {
                    crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);
                }
            }

            byte lowByte = (byte)(crc & 0xFF); //低8位
            byte highByte = (byte)((crc >> 8) & 0xFF); //高8位

            return isBigEndian ? new[] { highByte, lowByte } : new[] { lowByte, highByte };
        }

        /// <summary>
        /// 报文校验
        /// </summary>
        /// <param name="data">数据</param>
        /// <param name="length">长度</param>
        /// <returns></returns>
        /// <exception cref="InvalidDataException"></exception>
        protected override byte[] MessageCheck(byte[] data, int length)
        {
            if (data.Length != length)
                throw new InvalidDataException("接收报文长度异常！");

            ReadOnlySpan<byte> frame = data;
            ReadOnlySpan<byte> receivedCrc = frame.Slice(length - 2, 2);
            ReadOnlySpan<byte> calculatedCrc = Crc16(frame[..(length - 2)]);
            if (!receivedCrc.SequenceEqual(calculatedCrc))
                throw new InvalidDataException("CRC校验失败！");

            // 提取有效数据（去掉前2字节地址/功能码和后2字节CRC）
            return frame.Slice(2, length - 4).ToArray();
        }
    }
}
